﻿using System;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Input;
using System.Windows.Threading;
using System.Xml.Serialization;
using FMOD;
using LaunchPad.Core;
using LaunchPad.Core.Dialogs;
using Microsoft.Win32;

namespace LaunchPad
{
    // *************** REMEMBER TO PUT XBEE IN API MODE FIRST ***********************

    public partial class MainWindow : INotifyPropertyChanged
    {
        private const string AudioFileFilter = "Audio Files (*.wav,*.mp3)|*.wav;*.mp3|All (*.*)|*.*";
        private const string LaunchPadFileFilter = "LaunchPad Files (*.lpx)|*.lpx";
        private LaunchPadSettings _settings;
        private Field _field;
        private TimeSpan _lastTime;
        private PlaybackState _playbackState = PlaybackState.Stopped;

        private AudioTrack _audioTrack;
        private string _openFile;

        public MainWindow()
        {
            InitializeComponent();
            Loaded += MainWindowLoaded;
            Closed += MainWindowClosed;
        }

        void MainWindowLoaded(object sender, RoutedEventArgs e)
        {
            try
            {
                Reset();

                if (!string.IsNullOrEmpty(_settings.SerialPort))
                    XBeeIO.Instance.SerialPortName = _settings.SerialPort;

                if (!XBeeIO.Instance.IsAvailable)
                    XBeeIO.Instance.TryFindXBee();

                if (XBeeIO.Instance.IsAvailable)
                    _settings.SerialPort = XBeeIO.Instance.SerialPortName;

                CommandBindings.Add(new CommandBinding(ApplicationCommands.New, New));
                CommandBindings.Add(new CommandBinding(ApplicationCommands.Open, Open));
                CommandBindings.Add(new CommandBinding(ApplicationCommands.Save, Save, CanSave));
                CommandBindings.Add(new CommandBinding(ApplicationCommands.SaveAs, SaveAs, CanSaveAs));

                _runningDock.Hide();
            }
            catch(IOException ex)
            {
                var dialog = new ExceptionMessageBox {Message = ex.ToString(), Owner = this};
                dialog.ShowDialog();
            }
        }

        public void Reset()
        {
            Stop();
            AudioTrack = null;
            Field = new Field();
            SetupDataContext();
            Field.Tracks.Add(new TimeTrack());

            _settings = new LaunchPadSettings();
            _settings.Reload();

            if(_settings.Products == null)
                _settings.Products = new ProductCollection();

            _settings.Save();

            ProductFactory.Products = _settings.Products;
            ProductFactory.Instance.PropertyChanged += ProductsPropertyChanged;

            OpenFile = null;
        }

        void ProductsPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (_settings != null)
                _settings.Save();
        }

        public Field Field
        {
            get { return _field; }
            set
            {
                if (_field != value)
                {
                    _field = value;
                    OnPropertyChanged("Field");
                }
            }
        }

        public AudioTrack AudioTrack
        {
            get { return _audioTrack; }
            set
            {
                if (_audioTrack != value)
                {
                    _audioTrack = value;
                    OnPropertyChanged("AudioTrack");
                }
            }
        }

        private PlaybackState PlaybackState
        {
            get { return _playbackState; }
            set
            {
                if (_playbackState != value)
                {
                    switch (value)
                    {
                        case PlaybackState.Playing:
                            _playButton.Content = "Pause";
                            _startShowButton.IsEnabled = false;
                            _stopButton.IsEnabled = true;
                            AudioTrack.Paused = false;
                            _runningControl.SetPaused(false);
                            break;
                        case PlaybackState.Stopped:
                        case PlaybackState.Paused:
                            _playButton.Content = "Play";
                            _stopButton.IsEnabled = (value == PlaybackState.Paused);
                            AudioTrack.Paused = true;
                            break;
                    }

                    if (value == PlaybackState.Stopped)
                    {
                        _runningDock.Hide();
                        _startShowButton.IsEnabled = true;
                    }

                    _playbackState = value;
                    OnPropertyChanged("PlaybackState");
                }
            }
        }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        private void MainWindowClosed(object sender, EventArgs e)
        {
            Stop();

            foreach (Node node in Field.Nodes)
                node.IsSelected = false;

            _settings.Save();
        }

        private void SetupDataContext()
        {
            _nodeStack.DataContext = this;
            _networkControl.DataContext = Field;
            _library.DataContext = Field;
            _map.DataContext = Field;
        }

        private void TrackPositionChanged(object sender, EventArgs e)
        {
            if (PlaybackState == PlaybackState.Loading)
                return;

            TimeSpan t = AudioTrack.Position;

            _timeLabel.Dispatcher.BeginInvoke(DispatcherPriority.Normal,(Action) (() => UpdateTimeLabel(t)));
            Dispatcher.BeginInvoke(DispatcherPriority.Normal, (Action) (() => UpdateEvents(t)));
        }

        private void UpdateTimeLabel(TimeSpan time)
        {
            if (AudioTrack == null)
                return;

            string timeText = time.ToString("hh\\:mm\\:ss\\.f");
            _timeLabel.Content = timeText;

            // round to seconds
            var lapsedS = Math.Floor(time.TotalSeconds);
            var totalS = AudioTrack.Length.TotalSeconds;
            _runningControl.Lapsed = TimeSpan.FromSeconds(lapsedS);
            _runningControl.Remaining = TimeSpan.FromSeconds(totalS - lapsedS);
        }

        private void UpdateEvents(TimeSpan time)
        {
            if (Field == null)
                return;

            bool rewinding = (_lastTime > time);

            var effectEvents =
                Field.Events.OfType<EffectTrackEvent>().Where(
                    ev => ev.Effect.Enabled).ToList();

            // find any events that should be in a waiting state (e.g. user rewinded)
            // however, only do this if time is moving backwards (I know, weird)
            if (rewinding)
            {
                var waitingEvents =
                    from ete in effectEvents
                    where ete.Start > time
                    select ete;

                foreach (var ete in waitingEvents)
                {
                    ete.Effect.IsHot = false;
                    ete.State = TrackEventState.Waiting;
                }
            }
            else
            {
                // find any events that have been waiting and now should be firing
                // but only if time is moving forward
                var firingEvents =
                    from ete in effectEvents
                    where ete.State == TrackEventState.Waiting &&
                          ete.Start < time
                    select ete;

                foreach (var ete in firingEvents)
                {
                    ete.Effect.IsHot = true;
                    ete.State = TrackEventState.Active;
                }

                // find any events that are finished firing
                var finishedEvents =
                    from ete in effectEvents
                    where ete.State == TrackEventState.Active &&
                          ete.Finish < time
                    select ete;

                foreach (var ete in finishedEvents)
                {
                    if (!ete.Effect.Timeline.Intersects(time))
                        ete.Effect.IsHot = false;
                    ete.State = TrackEventState.Finished;
                }
            }

            _lastTime = time;
        }

        private void New(object target, ExecutedRoutedEventArgs e)
        {
            Reset();
        }

        private void CanSave(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = File.Exists(OpenFile);
        }

        private void Save(object target, ExecutedRoutedEventArgs e)
        {
            Save(OpenFile);
        }

        private void SaveAs(object target, ExecutedRoutedEventArgs e)
        {
            var dialog = new SaveFileDialog {Filter = LaunchPadFileFilter, DefaultExt = ".lpx"};

            if (dialog.ShowDialog().Value)
            {
                Save(dialog.FileName);
                OpenFile = dialog.FileName;
            }
        }

        private void Save(string file)
        {
            using (var stream = new FileStream(file, FileMode.Create, FileAccess.Write))
            {
                var serializer = new XmlSerializer(typeof(Field));
                serializer.Serialize(stream, Field);
            }
        }

        private static void CanSaveAs(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = true;
        }

        private void Open(object target, ExecutedRoutedEventArgs e)
        {
            var dialog = new OpenFileDialog {Filter = LaunchPadFileFilter};
            if (dialog.ShowDialog().Value)
            {
                Open(dialog.FileName);
            }
        }

        public void Open(string file)
        {
            try
            {
                using (var stream = new FileStream(file, FileMode.Open, FileAccess.Read))
                {
                    var serializer = new XmlSerializer(typeof (Field));
                    Field = (Field) serializer.Deserialize(stream);

                    if (!string.IsNullOrWhiteSpace(Field.AudioFile))
                    {
                        if (File.Exists(Field.AudioFile))
                        {
                            LoadAudio(Field.AudioFile);
                        }
                        else if (MessageBox.Show("Couldn't find audio file.  Would you like to locate it?",
                                                 "LaunchPad", MessageBoxButton.OKCancel) == MessageBoxResult.OK)
                        {
                            ImportAudio();
                        }
                    }

                    OpenFile = file;

                    SetupDataContext();
                }

                Field.Reset();
            }
            catch (IOException)
            {
                MessageBox.Show("Unable to open file");
            }
        }

        private string OpenFile
        {
            get { return _openFile; }

            set
            {
                string assemblyName = Assembly.GetExecutingAssembly().GetName().Name;

                Title = value == null ? assemblyName : string.Format("{0} - {1}", value, assemblyName);

                _openFile = value;

                PlaybackState = PlaybackState.Stopped;
            }
        }

        protected void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        private void PlayButtonClick(object sender, RoutedEventArgs e)
        {
            switch (PlaybackState)
            {
                case PlaybackState.Playing:
                    PlaybackState = PlaybackState.Paused;
                    break;
                case PlaybackState.Stopped:
                case PlaybackState.Paused:
                    Play();
                    break;
            }
        }

        private void Play()
        {
            PlaybackState = PlaybackState.Playing;
        }

        private void StopButtonClick(object sender, RoutedEventArgs e)
        {
            Stop();
        }

        private void Stop()
        {
            _runningDock.Hide();
            if (Field != null) 
                Field.ResetEvents();
            PlaybackState = PlaybackState.Stopped;
            if (AudioTrack != null)
                AudioTrack.Stop();
            if (Field != null) 
                Field.IsArmed = false;
        }

        private void ImportAudioMenuItemClick(object sender, RoutedEventArgs e)
        {
            ImportAudio();
        }

        private void ImportAudio()
        {
            var dialog = new OpenFileDialog {Filter = AudioFileFilter};

            if (dialog.ShowDialog().Value)
            {
                LoadAudio(dialog.FileName);
            }
        }

        private void LoadAudio(string file)
        {
            PlaybackState = PlaybackState.Loading;
            AudioTrack = new AudioTrack(file);
            AudioTrack.PositionChanged += TrackPositionChanged;
            AudioTrack.Stopped += TrackSoundFinished;
            PlaybackState = PlaybackState.Stopped;

            _playButton.IsEnabled = true;
            _startShowButton.IsEnabled = true;
            Field.AudioFile = file;
        }

        private void TrackSoundFinished(object sender, EventArgs e)
        {
            Dispatcher.BeginInvoke(DispatcherPriority.Normal,
                                   (Action) delegate { PlaybackState = PlaybackState.Stopped; });
        }

        private void StartShowButtonClick(object sender, RoutedEventArgs e)
        {
            Stop();


            bool confirm;
            if (Field.IsFullyAssigned)
                confirm = true;
            else
            {
                confirm =
                    MessageBox.Show("Some devices have not been assigned to a node", "Warning",
                                    MessageBoxButton.OKCancel) == MessageBoxResult.OK;
            }

            if (confirm && !Field.IsFullyConnected)
            {
                confirm =
                    MessageBox.Show("Some nodes have not been discovered", "Warning", MessageBoxButton.OKCancel) ==
                    MessageBoxResult.OK;
            }

            if (confirm)
            {
                var dialog = new StartShowDialog
                                 {
                                     WindowStartupLocation = WindowStartupLocation.CenterScreen,
                                     Owner = this
                                 };

                var result = dialog.ShowDialog();

                if (result.HasValue && result.Value)
                {
                    Field.Initialize();
                    Field.IsArmed = true;
                    Play();
                    _runningDock.Show();
                }
            }
        }

        void RunningDialogPlay(object sender, EventArgs e)
        {
            PlaybackState = PlaybackState.Playing;
        }

        void RunningDialogPause(object sender, EventArgs e)
        {
            PlaybackState = PlaybackState.Paused;
        }

        private void RunningDialogAbort(object sender, EventArgs e)
        {
            Stop();
        }

        private void ExitMenuItemClick(object sender, RoutedEventArgs e)
        {
            Close();
        }
    }
}